﻿using System;

namespace CS.Scheduling
{

    /// <summary>
    /// 一个给定的作业调度的封装
    /// <para>Encapsulates the scheduling for a given job.</para>
    /// </summary>
    public class Job
    {
        
        #region 公有属性

        /// <summary>
        /// 任务的全局唯一Id
        /// </summary>
        public string JobId { get; set; }

        /// <summary>
        /// 用任务本身做同步凭据。
        /// <para>Gets a synchronization token for the job.</para>
        /// </summary>
        public object SyncRoot
        {
            get { return this; }
        }

        /// <summary>
        /// 任务开始时间，如果没有指定任务立即开始。
        /// <para>The designated start time for the job. If not set, the job starts immediately.</para>
        /// </summary>
        public virtual DateTimeOffset StartTime { get; set; }


        private TimeSpan? _interval;
        /// <summary>
        /// 任务的时间间隔，多次运行时需要
        /// <para>The interval of the job. Required if the job is to supposed multiple times.</para>
        /// </summary>
        public virtual TimeSpan? Interval
        {
            get { return _interval; }
            set
            {
                if (_interval.HasValue && _interval.Value.TotalMilliseconds < 0)
                {
                    var msg = "Invalid interval of {0} milliseconds. Interval must be a positive value or [null].";
                    msg = String.Format(msg, _interval.Value.TotalMilliseconds);
                    throw new ArgumentOutOfRangeException("value", msg);
                }
                lock (SyncRoot)
                {
                    _interval = value;
                }
            }
        }

        private int? _loops;
        /// <summary>
        /// 让该任务运行的次数。
        /// <para>The number of executed loops. If set, the job runs the specified number of times, unless it aborts because the <see cref="ExpirationTime"/> was set and causes the job to be cancelled earlier.</para>
        /// </summary>
        public virtual int? Loops
        {
            get { return _loops; }
            set
            {
                if (value.HasValue && value < 1)
                {
                    var msg = "Invalid number of _loops: {0}. Only numbers above zero or [null] are allowed.";
                    msg = String.Format(msg, value);
                    throw new ArgumentOutOfRangeException("value", msg);
                }
                lock (SyncRoot)
                {
                    _loops = value;
                }
            }
        }
        
        private DateTimeOffset? _expirationTime;
        /// <summary>
        /// 任务的过期时间，多次运行时的可选项。
        /// <para>The expiration time of the job. This date is optional  in case the job runs only a specified number of times,  or indefinitely.</para>
        /// </summary>
        public virtual DateTimeOffset? ExpirationTime
        {
            get { return _expirationTime; }
            set
            {
                if (value.HasValue && value < SystemTime.Now())
                {
                    const string msg = "Expiration time cannot be in the past";
                    throw new ArgumentOutOfRangeException("value", msg);
                }
                lock (SyncRoot)
                {
                    _expirationTime = value;
                }
            }
        }

        /// <summary>
        /// 任务当前状态。
        /// <para>The job's current state.</para>
        /// </summary>
        public virtual JobState State { get; protected internal set; }

        #endregion

        #region  两个构造

        /// <summary>
        /// 构造新任务，给一个默认的全局唯一Id。
        /// <para>Creates a new job, and generates a unique job ID on the fly.</para>
        /// </summary>
        public Job()
            : this(Guid.NewGuid().ToString())
        {}

        /// <summary>
        /// 使用自定的任务Id构造新任务
        /// <para>Initializes a new job with a given identifier.</para>
        /// </summary>
        /// <param name="jobId">A unique ID for the job. Unique  job IDs are not evaluated by the scheduler, the calling party must ensure unique IDs.</param>
        public Job(string jobId)
        {
            JobId = jobId;
        }

        #endregion

        #region 可重写的虚方法

        /// <summary>
        /// 配置任务运行计划。
        /// <para>Configures how often the job is being repeated.</para>
        /// </summary>
        public virtual JobSchedule Run
        {
            get { return new JobSchedule(this); }
        }

        /// <summary>
        /// 取消任务。
        /// <para>Cancels the job in order to have it removed during the next processing loop. This method can be invoked by clients in order to cancel job execution without having to reference the job's <see cref="Scheduler"/>.</para>
        /// </summary>
        public virtual void Cancel()
        {
            lock (this)
            {
                State = JobState.Canceled;
            }
        }
        
        /// <summary>
        /// 暂停任务。
        /// <para>Pauses the current job.</para>
        /// </summary>
        /// <returns>True if the job's <see cref="State"/> was <see cref="JobState.Active"/> and was changed to <see cref="JobState.Paused"/>. False if the job's <see cref="State"/> is not <see cref="JobState.Active"/>.
        /// </returns> 
        /// <exception cref="InvalidOperationException">If the job has no interval, and can thus not be rescheduled. For a job that runs just once, set the <see cref="StartTime"/> accordingly.</exception>
        public virtual bool Pause()
        {
            if (!Interval.HasValue)
            {
                const string msg = "Jobs without interval cannot be paused - the scheduler does not know how to schedule it once it is resumed.";
                throw new InvalidOperationException(msg);
            }

            lock (this)
            {
                if (State != JobState.Active) return false;
                State = JobState.Paused;
                return true;
            }
        }

        /// <summary>
        /// 恢复暂停的任务
        /// <para>Resumes a paused job.</para>
        /// </summary>
        /// <returns>
        /// True if the job's <see cref="State"/> was <see cref="JobState.Paused"/> and was changed to <see cref="JobState.Active"/>. False if the job's  <see cref="State"/> is not <see cref="JobState.Paused"/>.
        /// </returns>
        public virtual bool Resume()
        {
            lock (this)
            {
                if (State != JobState.Paused) return false;
                State = JobState.Active;
                return true;
            }
        }

        #endregion

    }


    /// <summary>
    /// 附加有状态对象的任务实现。
    /// A generic job implementation which allows to attach strongly typed state information directly to the  job.
    /// </summary>
    /// <typeparam name="T">The type of the job's
    /// <see cref="Data"/> object.</typeparam>
    public class Job<T> : Job
    {
        /// <summary>
        /// 附加在任务上的状态对象
        /// <para>Gets or sets the state object that is attached to the job.</para>
        /// </summary>
        public T Data { get; set; }

        /// <summary>
        /// Creates a new job, and generates a unique job identifier on the fly.
        /// </summary>
        public Job()
        {}

        /// <summary>
        /// Initializes a new job with a given identifier.
        /// </summary>
        public Job(string id)
            : base(id)
        {}
    }


    /// <summary>
    /// 在执任务状态
    /// <para>State flags for maintainted jobs.</para>
    /// </summary>
    public enum JobState
    {
        /// <summary>
        /// 活动中。
        /// <para>The job is currently active.</para>
        /// </summary>
        Active,

        /// <summary>
        /// 暂停中。
        /// <para>The job was paused.</para>
        /// </summary>
        Paused,

        /// <summary>
        /// 执行完成，过期或者是次数达成。
        /// <para>The job was finished, either because it ran  the designated amount of times or expired.</para>
        /// </summary>
        Finished,

        /// <summary>
        /// 任务被取消。
        /// <para>The job was aborted.</para>
        /// </summary>
        Canceled
    }


    /// <summary>
    /// 计划任务策略，定义<see cref="Scheduler"/>类当发现系统时间变更后该如何执行计划任务。
    /// <para>Defines how the <see cref="Scheduler"/> class performs rescheduling if a changed system time was detected.</para>
    /// </summary>
    public enum ReschedulingStrategy
    {
        /// <summary>
        /// 系统时间变更后任务不重新安排。
        /// <para>Jobs are not rescheduled if a system time change was detected.</para>
        /// </summary>
        KeepFixedTimes,

        /// <summary>
        /// 任务的下次执行时间变更后修正过期时间或剩余次数。
        /// <para>Only the jobs' next execution time is rescheduled. The expiration time remains fixed.</para>
        /// </summary>
        RescheduleNextExecution,

        /// <summary>
        /// 下次执行时间次数和过期时间被同时修改后变更执行计划。
        /// <para>Both the jobs' next execution times and the expiration times are being shifted according to the time change.</para>
        /// </summary>
        RescheduleNextExecutionAndExpirationTime
    }


}